/*  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "options.hh"
#include "luge-logging.hh"
#include <iostream>
#include <cstring>
#include <getopt.h>

namespace {

enum {
  OPT_VERSION = 1000,
  OPT_OPENCL
};

/**
 * data for command line parsing.
 */
static struct option cmd_opts[] = {
  { "verbose", 0, 0, 'v' },
  { "quiet", 0, 0, 'q' },
  { "help", 0, 0, 'h' },
  { "opencl", 0, 0, OPT_OPENCL },
  { "version", 0, 0, OPT_VERSION },
  { 0, 0, 0, 0 }
};

/**
 * fill the luge internal options.
 * adjusts optind to point to the first option after the script name.
 */
static void collect_interpreter_opt
  (int argc, char **argv, luge::luge_opt &opt)
{
  int optc;
  opt = luge::luge_opt::defaults ();
  while ((optc = getopt_long (argc, argv, "+vhq", &cmd_opts[0], 0)) > 0) {
    switch (optc) {
    case 'v': opt.verbosity = 2; break;
    case 'q': opt.verbosity = 0; break;
    case 'h': opt.help = true; break;
    case OPT_VERSION: opt.display_version = true; break;
    case OPT_OPENCL: opt.enable_opencl = true; break;
    default: opt.found_errors = true; break;
    }
  }
  if (optind != argc) {
    opt.script_name = argv[optind];
    ++ optind;
  }
}

/**
 * fill the map with the script arguments from optind on.
 * adjust optind to point to the first unparsed argument.
 */
static void collect_script_opt
  (int argc, char **argv, luge::opt_map &map)
{
  while (optind != argc) {
    const char *arg = argv[optind];
    if (arg[0] == '-') {
      const char *opt_name = arg + 1;
      /*
       * ignore any second argument.
       */
      if (arg[1] == '-') {
        opt_name = arg + 2;
      }
      const char *eqp = strchr (opt_name, '=');
      if (eqp != 0) {
        /*
         * equal sign means the argument is attached to the option.
         */
        map[std::string (opt_name, eqp)] .push_back (eqp + 1);
        optind += 1;
      } else if (optind + 1 != argc && argv[optind + 1][0] != '-') {
        /*
         * following token does not start with a '-' => it is an argument.
         */
        map[opt_name].push_back (argv[optind + 1]);
        optind += 2;
      } else {
        /*
         * option without argument.
         */
        map[opt_name].push_back ("1");
        optind += 1;
      }
    } else {
      /*
       * non-option argument goes into the args-array.
       */
      map["args"].push_back (arg);
      optind += 1;
    }
  }
}

} /* local namespace */

luge::luge_opt luge::luge_opt::defaults ()
{
  luge_opt opts;
  opts.verbosity = 1;
  opts.help = false;
  opts.enable_opencl = false;
  opts.display_version = false;
  opts.found_errors = false;
  opts.script_name.clear ();
  return opts;
}

luge::options luge::parse_args (int argc, char **argv)
{
  luge::options options;
  collect_interpreter_opt (argc, argv, options.interpreter_opt);
  collect_script_opt (argc, argv, options.script_opt);
  return options;
}

namespace {

/**
 * put the list of strings as a table onto the stack.
 */
static void push_string_list (lua_State *ls, const luge::string_list &list)
{
  lua_newtable (ls);
  const int tbl_idx = lua_absindex (ls, -1);
  luge::string_list::const_iterator sit = list.begin ();
  int idx = 1;
  while (sit != list.end ()) {
    lua_pushnumber (ls, idx);
    lua_pushstring (ls, sit->c_str ());
    lua_settable (ls, tbl_idx);
    ++ idx;
    ++ sit;
  }
}

} /* namespace */

void luge::bind_args (lua_State *ls, const opt_map &map)
{
  opt_map::const_iterator mit = map.begin ();
  while (mit != map.end ()) {
    /*
     * do not overwrite variables.
     */
    lua_getglobal (ls, mit->first.c_str ());
    if (lua_isnil (ls, -1)) {
      push_string_list (ls, mit->second);
      lua_setglobal (ls, mit->first.c_str ());
    } else {
      lg::error () << "warning: will not overwrite '" << mit->first << "'\n";
    }
    lua_pop (ls, 1);
    ++ mit;
  }
}

void luge::usage (const std::string &prog)
{
  std::cout << "usage: " << prog << " [OPTS...] <script> [SCRIPT-ARGS...]\n";
  std::cout << "options (OPTS):\n"
            << "  --help, -h         display help message.\n"
            << "  --version          display version info.\n"
            << "  --verbose, -v      be verbose.\n"
            << "  --quiet, -q        be quiet.\n"
            << "  --opencl           enable OpenCL.\n";
  std::cout << "  <script> is the lua script to be executed.\n";
  std::cout << "recognized SCRIPT-ARGS:\n"
            << "  -<VAR>             append 1 to variable VAR.\n"
            << "  -<VAR> <VALUE>,\n"
            << "  -<VAR>=<VALUE>     append VALUE to variable VAR.\n"
            << "  <ARG>              append ARG to variable 'args'.\n";
}
